/*-------------------------------------------------------------------------
 *
 * tsginidx.c
 *	 GIN 支持函数 tsvector_ops
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsginidx.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/gin.h"
#include "access/stratnum.h"
#include "miscadmin.h"
#include "tsearch/ts_type.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"


Datum gin_cmp_tslexeme(PG_FUNCTION_ARGS)
{
	text	   *fc_a = PG_GETARG_TEXT_PP(0);
	text	   *fc_b = PG_GETARG_TEXT_PP(1);
	int			fc_cmp;

	fc_cmp = tsCompareString(VARDATA_ANY(fc_a), VARSIZE_ANY_EXHDR(fc_a),
						  VARDATA_ANY(fc_b), VARSIZE_ANY_EXHDR(fc_b),
						  false);

	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);
	PG_RETURN_INT32(fc_cmp);
}

Datum gin_cmp_prefix(PG_FUNCTION_ARGS)
{
	text	   *fc_a = PG_GETARG_TEXT_PP(0);
	text	   *fc_b = PG_GETARG_TEXT_PP(1);

#ifdef NOT_USED
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);
	Pointer		fc_extra_data = PG_GETARG_POINTER(3);
#endif
	int			fc_cmp;

	fc_cmp = tsCompareString(VARDATA_ANY(fc_a), VARSIZE_ANY_EXHDR(fc_a),
						  VARDATA_ANY(fc_b), VARSIZE_ANY_EXHDR(fc_b),
						  true);

	if (fc_cmp < 0)
		fc_cmp = 1;				/* 防止继续扫描 */

	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);
	PG_RETURN_INT32(fc_cmp);
}

Datum gin_extract_tsvector(PG_FUNCTION_ARGS)
{
	TSVector	fc_vector = PG_GETARG_TSVECTOR(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	Datum	   *fc_entries = NULL;

	*fc_nentries = fc_vector->size;
	if (fc_vector->size > 0)
	{
		int			fc_i;
		WordEntry  *fc_we = ARRPTR(fc_vector);

		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_vector->size);

		for (fc_i = 0; fc_i < fc_vector->size; fc_i++)
		{
			text	   *fc_txt;

			fc_txt = cstring_to_text_with_len(STRPTR(fc_vector) + fc_we->pos, fc_we->len);
			fc_entries[fc_i] = PointerGetDatum(fc_txt);

			fc_we++;
		}
	}

	PG_FREE_IF_COPY(fc_vector, 0);
	PG_RETURN_POINTER(fc_entries);
}

Datum gin_extract_tsquery(PG_FUNCTION_ARGS)
{
	TSQuery		fc_query = PG_GETARG_TSQUERY(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);

	/* StrategyNumber strategy = PG_GETARG_UINT16(2); */
	bool	  **fc_ptr_partialmatch = (bool **) PG_GETARG_POINTER(3);
	Pointer   **fc_extra_data = (Pointer **) PG_GETARG_POINTER(4);

	/* bool   **nullFlags = (bool **) PG_GETARG_POINTER(5); */
	int32	   *fc_searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *fc_entries = NULL;

	*fc_nentries = 0;

	if (fc_query->size > 0)
	{
		QueryItem  *fc_item = GETQUERY(fc_query);
		int32		fc_i,
					fc_j;
		bool	   *fc_partialmatch;
		int		   *fc_map_item_operand;

		/*
		 * 如果查询没有任何需要的正匹配（例如，它类似于'! foo'），
		 * 我们必须进行完整索引扫描。
		 */
		if (tsquery_requires_match(fc_item))
			*fc_searchMode = GIN_SEARCH_MODE_DEFAULT;
		else
			*fc_searchMode = GIN_SEARCH_MODE_ALL;

		/* 计算VAL项的数量 */
		fc_j = 0;
		for (fc_i = 0; fc_i < fc_query->size; fc_i++)
		{
			if (fc_item[fc_i].type == QI_VAL)
				fc_j++;
		}
		*fc_nentries = fc_j;

		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_j);
		fc_partialmatch = *fc_ptr_partialmatch = (bool *) palloc(sizeof(bool) * fc_j);

		/*
		 * 制作映射，将项的编号转换为相应操作数（相同，项）的编号。
		 * 项的编号在一致性方法中的检查数组中使用。我们为每个项使用相同的映射。
		 */
		*fc_extra_data = (Pointer *) palloc(sizeof(Pointer) * fc_j);
		fc_map_item_operand = (int *) palloc0(sizeof(int) * fc_query->size);

		/* 现在重新扫描VAL项并填充数组 */
		fc_j = 0;
		for (fc_i = 0; fc_i < fc_query->size; fc_i++)
		{
			if (fc_item[fc_i].type == QI_VAL)
			{
				QueryOperand *fc_val = &fc_item[fc_i].qoperand;
				text	   *fc_txt;

				fc_txt = cstring_to_text_with_len(GETOPERAND(fc_query) + fc_val->distance,
											   fc_val->length);
				fc_entries[fc_j] = PointerGetDatum(fc_txt);
				fc_partialmatch[fc_j] = fc_val->prefix;
				(*fc_extra_data)[fc_j] = (Pointer) fc_map_item_operand;
				fc_map_item_operand[fc_i] = fc_j;
				fc_j++;
			}
		}
	}

	PG_FREE_IF_COPY(fc_query, 0);

	PG_RETURN_POINTER(fc_entries);
}

typedef struct
{
	QueryItem  *first_item;
	GinTernaryValue *check;
	int		   *map_item_operand;
} GinChkVal;

/*
 * TS_execute回调，用于将tsquery操作数匹配到GIN索引数据
 */
static TSTernaryValue fc_checkcondition_gin(void *fc_checkval, QueryOperand *fc_val, ExecPhraseData *fc_data)
{
	GinChkVal  *fc_gcv = (GinChkVal *) fc_checkval;
	int			fc_j;
	GinTernaryValue fc_result;

	/* 将项的编号转换为相应条目的（操作数的）编号 */
	fc_j = fc_gcv->map_item_operand[((QueryItem *) fc_val) - fc_gcv->first_item];

	/* 确定当前条目在索引值中的存在 */
	fc_result = fc_gcv->check[fc_j];

	/*
	 * 如果使用了任何需要权重的val或调用者需要位置信息，
	 * 那么我们必须重新检查，因此将TRUE替换为MAYBE。
	 */
	if (fc_result == GIN_TRUE)
	{
		if (fc_val->weight != 0 || fc_data != NULL)
			fc_result = GIN_MAYBE;
	}

	/*
	 * 我们依赖于GinTernaryValue和TSTernaryValue使用等效值分配。
	 * 如果这不再成立，我们可以使用switch语句映射这些值，但这种情况不太可能发生。
	 */
	return (TSTernaryValue) fc_result;
}

Datum gin_tsquery_consistent(PG_FUNCTION_ARGS)
{
	bool	   *fc_check = (bool *) PG_GETARG_POINTER(0);

	/* StrategyNumber strategy = PG_GETARG_UINT16(1); */
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);

	/* int32 nkeys = PG_GETARG_INT32(3); */
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);
	bool		fc_res = false;

	/* 最初假设查询不需要重新检查 */
	*fc_recheck = false;

	if (fc_query->size > 0)
	{
		GinChkVal	fc_gcv;

		/*
		 * check-parameter数组为查询中的每个值（操作数）有一个条目。
		 */
		fc_gcv.first_item = GETQUERY(fc_query);
		StaticAssertStmt(sizeof(GinTernaryValue) == sizeof(bool),
						 "sizes of GinTernaryValue and bool are not equal");
		fc_gcv.check = (GinTernaryValue *) fc_check;
		fc_gcv.map_item_operand = (int *) (fc_extra_data[0]);

		switch (TS_execute_ternary(GETQUERY(fc_query),
								   &fc_gcv,
								   TS_EXEC_PHRASE_NO_POS,
								   fc_checkcondition_gin))
		{
			case TS_NO:
				fc_res = false;
				break;
			case TS_YES:
				fc_res = true;
				break;
			case TS_MAYBE:
				fc_res = true;
				*fc_recheck = true;
				break;
		}
	}

	PG_RETURN_BOOL(fc_res);
}

Datum gin_tsquery_triconsistent(PG_FUNCTION_ARGS)
{
	GinTernaryValue *fc_check = (GinTernaryValue *) PG_GETARG_POINTER(0);

	/* StrategyNumber strategy = PG_GETARG_UINT16(1); */
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);

	/* int32 nkeys = PG_GETARG_INT32(3); */
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	GinTernaryValue fc_res = GIN_FALSE;

	if (fc_query->size > 0)
	{
		GinChkVal	fc_gcv;

		/*
		 * check-parameter数组为查询中的每个值（操作数）有一个条目。
		 */
		fc_gcv.first_item = GETQUERY(fc_query);
		fc_gcv.check = fc_check;
		fc_gcv.map_item_operand = (int *) (fc_extra_data[0]);

		fc_res = TS_execute_ternary(GETQUERY(fc_query),
								 &fc_gcv,
								 TS_EXEC_PHRASE_NO_POS,
								 fc_checkcondition_gin);
	}

	PG_RETURN_GIN_TERNARY_VALUE(fc_res);
}

/*
 * 从前，gin_extract_tsvector只有两个参数。现在有三个，
 * 但我们仍然需要一个具有两个参数的pg_proc条目，以支持
 * 重新加载pre-9.1的contrib/tsearch2 opclass声明。
 * 这个兼容性函数最终应该被移除。（注意：你可能会说“嘿，但上面的代码只使用了两个参数，
 * 所以让我们只声明那样”。如果你这样尝试，你会发现opr_sanity回归测试会抱怨。）
 */
Datum gin_extract_tsvector_2args(PG_FUNCTION_ARGS)
{
	if (PG_NARGS() < 3)			/* 不应该发生 */
		elog(ERROR, "gin_extract_tsvector requires three arguments");
	return gin_extract_tsvector(fcinfo);
}

/*
 * 同样，我们需要声明具有五个参数的gin_extract_tsquery的存根版本。
 */
Datum gin_extract_tsquery_5args(PG_FUNCTION_ARGS)
{
	if (PG_NARGS() < 7)			/* 不应该发生 */
		elog(ERROR, "gin_extract_tsquery requires seven arguments");
	return gin_extract_tsquery(fcinfo);
}

/*
 * 同样，我们需要声明具有六个参数的gin_tsquery_consistent的存根版本。
 */
Datum gin_tsquery_consistent_6args(PG_FUNCTION_ARGS)
{
	if (PG_NARGS() < 8)			/* 不应该发生 */
		elog(ERROR, "gin_tsquery_consistent requires eight arguments");
	return gin_tsquery_consistent(fcinfo);
}

/*
 * 同样，声明带有不再被认为合适的参数类型的gin_extract_tsquery的存根版本。
 */
Datum gin_extract_tsquery_oldsig(PG_FUNCTION_ARGS)
{
	return gin_extract_tsquery(fcinfo);
}

/*
 * 同样，声明带有不再被认为合适的参数类型的gin_tsquery_consistent的存根版本。
 */
Datum gin_tsquery_consistent_oldsig(PG_FUNCTION_ARGS)
{
	return gin_tsquery_consistent(fcinfo);
}
